3.ExoPlayer源码分析之prepare方法

上面两篇文章说了ExoPlayer简单的使用,都是些API,没什么用,查查文档就可以了。我们要去学习ExoPlayer整体是怎么设计的。以下基于2.10.6版本.

找入口

分析的时候首先要找到一个切入点,上一篇文章中写了怎么去创建使用,我们再看一遍代码,如下:

View.inflate(context, R.layout.video_view, this)
mPlayer = ExoPlayerFactory.newSimpleInstance(context)
mPlayerView.player = mPlayer

val uri = Uri.parse(url)
val mediaSource = buildMediaSource(uri) //  ProgressiveMediaSource
mPlayer.prepare(mediaSource)

从上面的代码看出来其实主要就是三步:

  1. 通过ExoPlayerFactory创建ExoPlayer实例对象,当然这里创建的是SimpleExoPlayer()
  2. 将视频url封装成MediaSource类
  3. 调用ExoPlayer.prepare()方法,并将MediaSource类对象作为参数传入。

我们下面分析就只看上面的1和3部分,因为2最终也是通过3方法的参数传入的。

类图

先看一下ExoPlayer接口的实现:

/**
 * An extensible media player that plays MediaSources. Instances can be obtained from ExoPlayerFactory.
 */
public interface ExoPlayer extends Player {
    void retry();
    void prepare(MediaSource mediaSource);
    PlayerMessage createMessage(PlayerMessage.Target target);
}

它实现了Player接口:

public interface Player {
    interface AudioComponent
    interface VideoComponent
    interface TextComponent
    interface MetadataComponent
    interface MetadataComponent
    interface EventListener
    int STATE_IDLE = 1;
    int STATE_BUFFERING = 2;
    int STATE_READY = 3;
    int STATE_ENDED = 4;
    AudioComponent getAudioComponent();
    VideoComponent getVideoComponent();
    TextComponent getTextComponent();
     MetadataComponent getMetadataComponent();
     Looper getApplicationLooper();
     void addListener(EventListener listener);
     int getPlaybackState();
     boolean isPlaying();
     void setPlayWhenReady(boolean playWhenReady);
     void setRepeatMode();
     void setShuffleModeEnabled(boolean shuffleModeEnabled);
     void seekTo(long positionMs);
     void stop();
     void release();
     long getCurrentPosition();
     long getCurrentPosition();
}

Player接口有一个默认的实现类BasePlayer:

/** Abstract base {@link Player} which implements common implementation independent methods. */
public abstract class BasePlayer implements Player {

}

ExoPlayer创建部分

获取ExoPlayer对象的实例是通过ExoPlayerFactory来实现的,这里面提供了一些静态的方法:

public static SimpleExoPlayer newSimpleInstance(Context context) {
    return newSimpleInstance(context, new DefaultTrackSelector());
}

public static SimpleExoPlayer newSimpleInstance(Context context, TrackSelector trackSelector) {
    return newSimpleInstance(context, new DefaultRenderersFactory(context), trackSelector);
}

public static SimpleExoPlayer newSimpleInstance(
  Context context, RenderersFactory renderersFactory, TrackSelector trackSelector) {
    return newSimpleInstance(context, renderersFactory, trackSelector, new DefaultLoadControl());
}

public static ExoPlayer newInstance(
  Context context, Renderer[] renderers, TrackSelector trackSelector) {
    return newInstance(context, renderers, trackSelector, new DefaultLoadControl());
}

通过这个工厂类可以获取SimpleExoPlayer或者ExoPlayer接口的实例。我们上面是通过该工厂类的ExoPlayerFactory.newSimpleInstance(context)来获取的SimpleExoPlayer。而newSimpleInstance()方法的实现如下:

public static SimpleExoPlayer newSimpleInstance(
      Context context,
      RenderersFactory renderersFactory,
      TrackSelector trackSelector,
      LoadControl loadControl,
      @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
      BandwidthMeter bandwidthMeter,
      AnalyticsCollector.Factory analyticsCollectorFactory,
      Looper looper) {
    return new SimpleExoPlayer(
        context,
        renderersFactory,
        trackSelector,
        loadControl,
        drmSessionManager,
        bandwidthMeter,
        analyticsCollectorFactory,
        looper);
}

可以看到它最后调用了new SimpleExoPlayer,并且传入了一些参数,这些参数主要有:

  • TrackSelector:轨道提取器,The component of an ExoPlayer responsible for selecting tracks to be consumed by each of the player's Renderers. The DefaultTrackSelector implementation should be suitable for most use cases.从MediaSource中提取各个轨道的二进制数据,交给Renderer渲染,它有个selectTracks()方法,会返回TrackSelection数组,TrackSelection就是对轨道进行解析的,因为一个文件有多个轨道:音频轨、视频轨、文字轨等,所以也需要多个TrackSelection。
  • Renderer:对多媒体中的各个轨道(音轨、视频轨、字母轨等)数据进行渲染,渲染就是"播放",把二进制文件渲染成声音、画面,创建播放器时传入。 RenderersFactory的作用就是创建Renderers,每个资源可能有音频、视频等多个轨道,每个Render对应一个轨道,Renderer对应的有VideoRender、AudioRender、TextRenderer、MetadataRenderers等等
  • LoadControl:对MediaSource进行控制,比如什么时候开始缓冲、缓冲多少等 主要是记录一些位置。
  • MediaSource:定义多媒体数据源,这个类的功能就是从Uri中读取多媒体文件的二进制数据。MediaSource在播放开始时通过ExoPlayer.prepare()注入,它有一个主要的方法就是createPeriod(),用来创建Period对象,这个对象里面会真正的做资源处理。
  • Looper:为了ExoPlayer中消息的处理
  • BandwidthMeter:监控当前的带宽情况

它们之间的关系是:

渲染器(Render) ---刷数据--->提取器(Extraor) ----读取数据---> 加载控制器(LoadControl) ----控制数据加载方式---> 媒体源(MediaSource)

扯远了,继续看SimpleExoPlayer的构造函数:

public class SimpleExoPlayer extends BasePlayer
    implements ExoPlayer,
        Player.AudioComponent,
        Player.VideoComponent,
        Player.TextComponent,
        Player.MetadataComponent {
    private final ExoPlayerImpl player;
    private final Handler eventHandler;

    protected SimpleExoPlayer(
          Context context,
          RenderersFactory renderersFactory,
          TrackSelector trackSelector, // 轨道提取器
          LoadControl loadControl, // 加载控制器
          @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,  // drm session管理 刚才传的是null
          BandwidthMeter bandwidthMeter, // 带宽监测
          AnalyticsCollector.Factory analyticsCollectorFactory, // 用于分析
          Clock clock, // 传的是SystemClick,可以获取系统时间,为了创建HandlerWrapper
          Looper looper) {
        this.bandwidthMeter = bandwidthMeter;
        componentListener = new ComponentListener();
        videoListeners = new CopyOnWriteArraySet<>();
        audioListeners = new CopyOnWriteArraySet<>();
        textOutputs = new CopyOnWriteArraySet<>();
        metadataOutputs = new CopyOnWriteArraySet<>();
        videoDebugListeners = new CopyOnWriteArraySet<>();
        audioDebugListeners = new CopyOnWriteArraySet<>();
        eventHandler = new Handler(looper);
        // 获取Render来进行渲染
        renderers =
            renderersFactory.createRenderers(
                eventHandler,
                componentListener,
                componentListener,
                componentListener,
                componentListener,
                drmSessionManager);

        // Set initial values.
        audioVolume = 1;
        audioSessionId = C.AUDIO_SESSION_ID_UNSET;
        audioAttributes = AudioAttributes.DEFAULT;
        videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
        currentCues = Collections.emptyList();

        // 创建ExoPlayerImp的实例,并将render trackselector loadControl bandwidthmeter looper等传入
        // Build the player and associated objects.
        player =
            new ExoPlayerImpl(renderers, trackSelector, loadControl, bandwidthMeter, clock, looper);
        analyticsCollector = analyticsCollectorFactory.createAnalyticsCollector(player, clock);
        addListener(analyticsCollector);
        addListener(componentListener);
        videoDebugListeners.add(analyticsCollector);
        videoListeners.add(analyticsCollector);
        audioDebugListeners.add(analyticsCollector);
        audioListeners.add(analyticsCollector);
        addMetadataOutput(analyticsCollector);
        bandwidthMeter.addEventListener(eventHandler, analyticsCollector);
        if (drmSessionManager instanceof DefaultDrmSessionManager) {
          ((DefaultDrmSessionManager) drmSessionManager).addListener(eventHandler, analyticsCollector);
        }
        audioFocusManager = new AudioFocusManager(context, componentListener);
    }
}

该方法里面调用了new ExoPlayerImpl(),继续看一下ExoPlayerImpl的构造函数:

final class ExoPlayerImpl extends BasePlayer implements ExoPlayer {
    public ExoPlayerImpl(
      Renderer[] renderers,
      TrackSelector trackSelector,
      LoadControl loadControl,
      BandwidthMeter bandwidthMeter,
      Clock clock,
      Looper looper) {
    Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
        + ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
    Assertions.checkState(renderers.length > 0);
    this.renderers = Assertions.checkNotNull(renderers);
    this.trackSelector = Assertions.checkNotNull(trackSelector);
    this.playWhenReady = false;
    this.repeatMode = Player.REPEAT_MODE_OFF;
    this.shuffleModeEnabled = false;
    this.listeners = new CopyOnWriteArrayList<>();
    emptyTrackSelectorResult =
        new TrackSelectorResult(
            new RendererConfiguration[renderers.length],
            new TrackSelection[renderers.length],
            null);
    // 媒体资源的片段,TimeLine和Period都是媒体资源片段状态相关的        
    period = new Timeline.Period();
    // 播放器相关参数,default是默认的1倍速
    playbackParameters = PlaybackParameters.DEFAULT;
    // seek的参数
    seekParameters = SeekParameters.DEFAULT;
    playbackSuppressionReason = PLAYBACK_SUPPRESSION_REASON_NONE;
    // 通过传递进来的looper创建eventHandler,并将message交给ExoPlayerImpl.this.handleEvent方法
    eventHandler =
        new Handler(looper) {
          @Override
          public void handleMessage(Message msg) {
            ExoPlayerImpl.this.handleEvent(msg);
          }
        };
    // 创建一个空的播放对象信息,直到开播的时候再赋值
    playbackInfo = PlaybackInfo.createDummy(/* startPositionUs= */ 0, emptyTrackSelectorResult);
    pendingListenerNotifications = new ArrayDeque<>();
    // 创建ExoPlayerImplInternal对象internalPlayer
    internalPlayer =
        new ExoPlayerImplInternal(
            renderers,
            trackSelector,
            emptyTrackSelectorResult,
            loadControl,
            bandwidthMeter,
            playWhenReady,
            repeatMode,
            shuffleModeEnabled,
            eventHandler,
            clock);
    // 创建internalPlayer使用的handler        
    internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
    }

}

里面又创建了ExoPlayerImplInternal,并把那些参数全部传递到这里面了:

public ExoPlayerImplInternal(
      Renderer[] renderers,
      TrackSelector trackSelector,
      TrackSelectorResult emptyTrackSelectorResult,
      LoadControl loadControl,
      BandwidthMeter bandwidthMeter,
      boolean playWhenReady,
      @Player.RepeatMode int repeatMode,
      boolean shuffleModeEnabled,
      Handler eventHandler,
      Clock clock) {
    this.renderers = renderers;
    this.trackSelector = trackSelector;
    this.emptyTrackSelectorResult = emptyTrackSelectorResult;
    this.loadControl = loadControl;
    this.bandwidthMeter = bandwidthMeter;
    this.playWhenReady = playWhenReady;
    this.repeatMode = repeatMode;
    this.shuffleModeEnabled = shuffleModeEnabled;
    this.eventHandler = eventHandler;
    this.clock = clock;
    this.queue = new MediaPeriodQueue();

    backBufferDurationUs = loadControl.getBackBufferDurationUs();
    retainBackBufferFromKeyframe = loadControl.retainBackBufferFromKeyframe();

    seekParameters = SeekParameters.DEFAULT;
    playbackInfo =
        PlaybackInfo.createDummy(/* startPositionUs= */ C.TIME_UNSET, emptyTrackSelectorResult);
    playbackInfoUpdate = new PlaybackInfoUpdate();
    rendererCapabilities = new RendererCapabilities[renderers.length];
    for (int i = 0; i < renderers.length; i++) {
      renderers[i].setIndex(i);
      rendererCapabilities[i] = renderers[i].getCapabilities();
    }
    mediaClock = new DefaultMediaClock(this, clock);
    pendingMessages = new ArrayList<>();
    enabledRenderers = new Renderer[0];
    // Timeline、Window、Period后面的文章会单独讲一下
    window = new Timeline.Window();
    period = new Timeline.Period();
    // 初始化trackSelector
    trackSelector.init(/* listener= */ this, bandwidthMeter);

    // Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
    // not normally change to this priority" is incorrect.
    internalPlaybackThread = new HandlerThread("ExoPlayerImplInternal:Handler",
        Process.THREAD_PRIORITY_AUDIO);
    // 启动一个线程,准备接受任务    
    internalPlaybackThread.start();
    // 这个地方比较关键clock.createHandler的意思是使用特定的looper和特定callback来处理消息
    // 而这里传的callback是this,所以通过该handler发送的消息都会传递到ExoPlayerImplInternal类的handleMessage方法
    // clock是一个可以获取系统时间的类,又通过clock创建了一个ExoPlayerImplInternal使用的handler
    handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
  }

看到这里应该明白了其实ExoPlayerImplInternal才是最终实现的核心类。
到这里我们上面mPlayer = ExoPlayerFactory.newSimpleInstance(context)的部分已经看完了,剩下的就是mPlayer.prepare(concatenatingMediaSource).

ExoPlayer.prepare()部分

我们找到SimpleExoPlayer.prepare()方法:

  @Override
  public void prepare(MediaSource mediaSource) {
    prepare(mediaSource, /* resetPosition= */ true, /* resetState= */ true);
  }

  @Override
  public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
    verifyApplicationThread();
    if (this.mediaSource != null) {
      this.mediaSource.removeEventListener(analyticsCollector);
      analyticsCollector.resetForNewMediaSource();
    }
    this.mediaSource = mediaSource;
    mediaSource.addEventListener(eventHandler, analyticsCollector);
    @AudioFocusManager.PlayerCommand
    int playerCommand = audioFocusManager.handlePrepare(getPlayWhenReady());
    // 更新当前是否是准备完成后自动开始播放
    updatePlayWhenReady(getPlayWhenReady(), playerCommand);
    // player是ExoPlayerImpl对象
    player.prepare(mediaSource, resetPosition, resetState);
  }

所以要继续看ExoPlayerImpl.prepare()方法:

  @Override
  public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
    playbackError = null;
    this.mediaSource = mediaSource;
    PlaybackInfo playbackInfo =
        getResetPlaybackInfo(
            resetPosition, resetState, /* playbackState= */ Player.STATE_BUFFERING);
    // Trigger internal prepare first before updating the playback info and notifying external
    // listeners to ensure that new operations issued in the listener notifications reach the
    // player after this prepare. The internal player can't change the playback info immediately
    // because it uses a callback.
    hasPendingPrepare = true;
    pendingOperationAcks++;
    // 调用了ExoPlayerImplInternal的prepare方法
    internalPlayer.prepare(mediaSource, resetPosition, resetState);
    updatePlaybackInfo(
        playbackInfo,
        /* positionDiscontinuity= */ false,
        /* ignored */ DISCONTINUITY_REASON_INTERNAL,
        TIMELINE_CHANGE_REASON_RESET,
        /* seekProcessed= */ false);
  }

所以要继续再看ExoPlayerImplInternal的prepare方法:

  public void prepare(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
    // 调用handler.sendMessage,上面在handler初始化的时候说过,该handler发送的消息会走到该类的handleMessage方法。
    handler
        .obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, resetState ? 1 : 0, mediaSource)
        .sendToTarget();
  }

所以接下来要继续看一下ExoPlayerImplInternal.handleMessage()方法,这个方法比较多,我们这里只要看MSG_PREPARE部分:

  public boolean handleMessage(Message msg) {
    try {
      switch (msg.what) {
        case MSG_PREPARE:
        // 通过msg取出MediaSource对象并作为参数调用prepareInternal()方法
          prepareInternal(
              (MediaSource) msg.obj,
              /* resetPosition= */ msg.arg1 != 0,
              /* resetState= */ msg.arg2 != 0);
          break;
        case MSG_SET_PLAY_WHEN_READY:
          setPlayWhenReadyInternal(msg.arg1 != 0);
          break;
        case MSG_SET_REPEAT_MODE:
          setRepeatModeInternal(msg.arg1);
          break;
        case MSG_SET_SHUFFLE_ENABLED:
          setShuffleModeEnabledInternal(msg.arg1 != 0);
          break;
        case MSG_DO_SOME_WORK:
          doSomeWork();
          break;
        ..........
  }

里面调用了prepareInternal()方法:

private void prepareInternal(MediaSource mediaSource, boolean resetPosition, boolean resetState) {
    pendingPrepareCount++;
    // 这个方法是做一些资源的释放初始化等操作,移除MSG_DO_SOME_WORK消息,然后重置Render,把之前的MediaSource释放等
    resetInternal(
        /* resetRenderers= */ false, /* releaseMediaSource= */ true, resetPosition, resetState);
    // onPrepared方法内部只是reset了
    loadControl.onPrepared();
    this.mediaSource = mediaSource;
    // 更新当前的状态到buffering
    setState(Player.STATE_BUFFERING);
    // 开始资源准备,并且添加对应的监听, 具体MediaSource里面怎么去实现的prepareSource,等我们讲完doSomeWork后再说
    mediaSource.prepareSource(/* listener= */ this, bandwidthMeter.getTransferListener());
    // 又发送了MSG_DO_SOME_WORK
    handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}

这里又是两部分:

  • mediaSource.prepareSource()方法,这里面是关于MediaSource作用的部分
  • 发送MSG_DO_SOME_WORK的消息

MediaSource.prepareSource()

在看之前有必要先看一下MediaSource接口:

Defines and provides media to be played by an ExoPlayer. A MediaSource has two main responsibilities:
To provide the player with a Timeline defining the structure of its media, and to provide a new timeline whenever the structure of the media changes. The MediaSource provides these timelines by calling MediaSource.SourceInfoRefreshListener.onSourceInfoRefreshed on the MediaSource.SourceInfoRefreshListeners passed to prepareSource(MediaSource.SourceInfoRefreshListener, TransferListener).
To provide MediaPeriod instances for the periods in its timeline. MediaPeriods are obtained by calling createPeriod(MediaSource.MediaPeriodId, Allocator, long), and provide a way for the player to load and read the media.

大体的意思就是一个MediaSource要有两个重要的职责:

  • 将一个定义媒体结构的Timeline对象提供给player,并且在媒体变化的时候去提供一个新的Timeline对象
  • 对Timeline中的媒体片段提供MediaPeriod对象,player通过MediaPeriod中提供的方式来读取媒体数据

继续看一下mediaSource.prepareSource()方法,它的实现是在BaseMediaSource类中的:

public final void prepareSource(
      SourceInfoRefreshListener listener,
      @Nullable TransferListener mediaTransferListener) {
    Looper looper = Looper.myLooper();
    Assertions.checkArgument(this.looper == null || this.looper == looper);
    sourceInfoListeners.add(listener);
    if (this.looper == null) {
      this.looper = looper;
      // 开始的时候会调用该方法
      prepareSourceInternal(mediaTransferListener);
    } else if (timeline != null) {
      listener.onSourceInfoRefreshed(/* source= */ this, timeline, manifest);
    }
}

继续看prepareSourceInternal()方法,该方法是子类去实现的,这里我们用普通视频格式的ProgressiveMediaSource类来看:

public void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
    transferListener = mediaTransferListener;
    notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable);
}

private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) {
    timelineDurationUs = durationUs;
    timelineIsSeekable = isSeekable;
    // TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223.
    refreshSourceInfo(
        new SinglePeriodTimeline(
            timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, tag),
        /* manifest= */ null);
}

protected final void refreshSourceInfo(Timeline timeline, @Nullable Object manifest) {
    this.timeline = timeline;
    this.manifest = manifest;
    for (SourceInfoRefreshListener listener : sourceInfoListeners) {
      listener.onSourceInfoRefreshed(/* source= */ this, timeline, manifest);
    }
 }

而这里的listener是谁呢? 是ExoPlayerImplInternal,继续看ExoPlayerImplInternal.onSourceInfoRefreshed()方法:

public void onSourceInfoRefreshed(MediaSource source, Timeline timeline, Object manifest) {
    handler.obtainMessage(MSG_REFRESH_SOURCE_INFO,
        new MediaSourceRefreshInfo(source, timeline, manifest)).sendToTarget();
  }

接着就是发送MSG_REFRESH_SOURCE_INFO消息,然后我们到handleMessage()中看一下:

  public boolean handleMessage(Message msg) {
    try {
      switch (msg.what) {
        case MSG_PREPARE:
          prepareInternal(
              (MediaSource) msg.obj,
              /* resetPosition= */ msg.arg1 != 0,
              /* resetState= */ msg.arg2 != 0);
          break;
          case MSG_REFRESH_SOURCE_INFO:
          handleSourceInfoRefreshed((MediaSourceRefreshInfo) msg.obj);
          break;
          ....
      }
  }
}

Message发送过来的是MediaSourceRefreshInfo对象,然后将其作为参数传递给handleSourceInfoRefreshed()方法,里面逻辑太多了,大体都是一些更新播放信息和状态的操作。

MSG_DO_SOME_WORK消息

handleMessage方法中在收到MSG_DO_SOME_WORK后调用了doSomeWork()方法,我们看一下他里面的实现:

private void doSomeWork() throws ExoPlaybackException, IOException {
    // 获取系统时间
    long operationStartTimeMs = clock.uptimeMillis();
    // 通过MediaPeriodQueue更新媒体文件的片段,里面会去prepare下一个片段
    updatePeriods();
    if (!queue.hasPlayingPeriod()) {
      // We're still waiting for the first period to be prepared.
      maybeThrowPeriodPrepareError();
      scheduleNextWork(operationStartTimeMs, PREPARING_SOURCE_INTERVAL_MS);
      return;
    }
    // 获取当前播放器的媒体片段
    MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();

    TraceUtil.beginSection("doSomeWork");
    // 更新播放位置
    updatePlaybackPositions();
    long rendererPositionElapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;

    playingPeriodHolder.mediaPeriod.discardBuffer(playbackInfo.positionUs - backBufferDurationUs,
        retainBackBufferFromKeyframe);

    boolean renderersEnded = true;
    boolean renderersReadyOrEnded = true;
    // renderers会比较多,有音频、视频、文本,Renderer接口有好几个实现类
    for (Renderer renderer : enabledRenderers) {
      // TODO: Each renderer should return the maximum delay before which it wishes to be called
      // again. The minimum of these values should then be used as the delay before the next
      // invocation of this method.
      // 开始让Renderer进入准备状态
      renderer.render(rendererPositionUs, rendererPositionElapsedRealtimeUs);
      renderersEnded = renderersEnded && renderer.isEnded();
      // Determine whether the renderer is ready (or ended). We override to assume the renderer is
      // ready if it needs the next sample stream. This is necessary to avoid getting stuck if
      // tracks in the current period have uneven durations. See:
      // https://github.com/google/ExoPlayer/issues/1874
      boolean rendererReadyOrEnded = renderer.isReady() || renderer.isEnded()
          || rendererWaitingForNextStream(renderer);
      if (!rendererReadyOrEnded) {
        renderer.maybeThrowStreamError();
      }
      renderersReadyOrEnded = renderersReadyOrEnded && rendererReadyOrEnded;
    }
    if (!renderersReadyOrEnded) {
      maybeThrowPeriodPrepareError();
    }

    long playingPeriodDurationUs = playingPeriodHolder.info.durationUs;
    if (renderersEnded
        && (playingPeriodDurationUs == C.TIME_UNSET
            || playingPeriodDurationUs <= playbackInfo.positionUs)
        && playingPeriodHolder.info.isFinal) {
      setState(Player.STATE_ENDED);
      stopRenderers();
    } else if (playbackInfo.playbackState == Player.STATE_BUFFERING
        && shouldTransitionToReadyState(renderersReadyOrEnded)) {
      setState(Player.STATE_READY);
      // prepare的时候会走到这里,如果prepared完成需要自动播放并且所有Renderer目前都已经是准备好的状态那就会开始渲染的功能
      if (playWhenReady) {
        startRenderers();
      }
    } else if (playbackInfo.playbackState == Player.STATE_READY
        && !(enabledRenderers.length == 0 ? isTimelineReady() : renderersReadyOrEnded)) {
      rebuffering = playWhenReady;
      setState(Player.STATE_BUFFERING);
      stopRenderers();
    }

    if (playbackInfo.playbackState == Player.STATE_BUFFERING) {
      for (Renderer renderer : enabledRenderers) {
        renderer.maybeThrowStreamError();
      }
    }

    if ((playWhenReady && playbackInfo.playbackState == Player.STATE_READY)
        || playbackInfo.playbackState == Player.STATE_BUFFERING) {
      // 发送下一个MSG_DO_SOME_WORK的消息,这样来达到循环
      scheduleNextWork(operationStartTimeMs, RENDERING_INTERVAL_MS);
    } else if (enabledRenderers.length != 0 && playbackInfo.playbackState != Player.STATE_ENDED) {
      scheduleNextWork(operationStartTimeMs, IDLE_INTERVAL_MS);
    } else {
      handler.removeMessages(MSG_DO_SOME_WORK);
    }

    TraceUtil.endSection();
}

可以看出,doSomeWork()方法会不断的去绘制,然后每次绘制完一部分就会继续循环调用该方法去绘制下一个媒体片段。上面可以看到核心功能在updatePeriods()方法,通过循环来不断的去加载下一个媒体片段,来达到播放的功能,所以接下来看一下该方法:

  private void updatePeriods() throws ExoPlaybackException, IOException {
    if (mediaSource == null) {
      // The player has no media source yet.
      return;
    }
    if (pendingPrepareCount > 0) {
      // We're waiting to get information about periods.
      mediaSource.maybeThrowSourceInfoRefreshError();
      return;
    }

    // Update the loading period if required.
    // 下面会看该方法的实现,这个方法就是去尝试加载下一个媒体片段
    maybeUpdateLoadingPeriod();

    MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
    if (loadingPeriodHolder == null || loadingPeriodHolder.isFullyBuffered()) {
      setIsLoading(false);
    } else if (!playbackInfo.isLoading) {
      maybeContinueLoading();
    }

    if (!queue.hasPlayingPeriod()) {
      // We're waiting for the first period to be prepared.
      return;
    }

    // Advance the playing period if necessary.
    MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
    MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
    boolean advancedPlayingPeriod = false;
    // 如果prepare完成后需要开始播放的话
    while (playWhenReady
        && playingPeriodHolder != readingPeriodHolder
        && rendererPositionUs >= playingPeriodHolder.getNext().getStartPositionRendererTime()) {
      // All enabled renderers' streams have been read to the end, and the playback position reached
      // the end of the playing period, so advance playback to the next period.
      if (advancedPlayingPeriod) {
        // If we advance more than one period at a time, notify listeners after each update.
        maybeNotifyPlaybackInfoChanged();
      }
      int discontinuityReason =
          playingPeriodHolder.info.isLastInTimelinePeriod
              ? Player.DISCONTINUITY_REASON_PERIOD_TRANSITION
              : Player.DISCONTINUITY_REASON_AD_INSERTION;
      MediaPeriodHolder oldPlayingPeriodHolder = playingPeriodHolder;
      // advancePlayingPeriod()方法内部会去设置queue中要播放的playingPeroidHolder
      // 这里对于MediaPeriodQueue有必要说一下,它里面有三个MediaPeriodHolder对象,分别是
      // playingMediaPeriodHolder、readingMediaPeriodHolder、loadingMediaPeriodHolder
      playingPeriodHolder = queue.advancePlayingPeriod();
      // 调用enableRenderers()方法,然后开始渲染
      updatePlayingPeriodRenderers(oldPlayingPeriodHolder);
      playbackInfo =
          playbackInfo.copyWithNewPosition(
              playingPeriodHolder.info.id,
              playingPeriodHolder.info.startPositionUs,
              playingPeriodHolder.info.contentPositionUs,
              getTotalBufferedDurationUs());
      playbackInfoUpdate.setPositionDiscontinuity(discontinuityReason);
      updatePlaybackPositions();
      advancedPlayingPeriod = true;
    }
    ........
  }

maybeUpdateLoadingPeriod()是用来检测是否加载下一个媒体片段的:

private void maybeUpdateLoadingPeriod() throws IOException {
    // 如果现在有一个正在loading的片段,会重新去评估
    queue.reevaluateBuffer(rendererPositionUs);
    // 检查是否需要加载下一个媒体片段
    if (queue.shouldLoadNextMediaPeriod()) {
      // 获取下一个媒体片段信息
      MediaPeriodInfo info = queue.getNextMediaPeriodInfo(rendererPositionUs, playbackInfo);
      if (info == null) {
        maybeThrowSourceInfoRefreshError();
      } else {
        // 1. 调用了MediaPeriodQueue.enqueueNextMediaPeriod()方法
        MediaPeriod mediaPeriod =
            queue.enqueueNextMediaPeriod(
                rendererCapabilities,
                trackSelector,
                loadControl.getAllocator(),
                mediaSource,
                info);
        // 2. 调用下一个媒体片段的prepare方法。MediaPeriod接口有很多实现类,例如DASH、HLS等
        // 所以每个不同类型的资源,它的prepare方法都是不一样的        
        mediaPeriod.prepare(this, info.startPositionUs);
        setIsLoading(true);
        // 更新状态
        handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
      }
    }
  }

上面又分了两部分:

  • 获取MediaPeriod
  • 调用MediaPeriod.prepare方法

先看获取MediaPeriod的部分

MediaPeriodQueue.enqueueNextMediaPeriod方法的实现如下:

  public MediaPeriod enqueueNextMediaPeriod(
      RendererCapabilities[] rendererCapabilities,
      TrackSelector trackSelector,
      Allocator allocator,
      MediaSource mediaSource,
      MediaPeriodInfo info) {
    long rendererPositionOffsetUs =
        loading == null
            ? (info.id.isAd() && info.contentPositionUs != C.TIME_UNSET
                ? info.contentPositionUs
                : 0)
            : (loading.getRendererOffset() + loading.info.durationUs - info.startPositionUs);

    // 创建MediaPeriodHolder    
    MediaPeriodHolder newPeriodHolder =
        new MediaPeriodHolder(
            rendererCapabilities,
            rendererPositionOffsetUs,
            trackSelector,
            allocator,
            mediaSource,
            info);
    if (loading != null) {
      Assertions.checkState(hasPlayingPeriod());
      loading.setNext(newPeriodHolder);
    }
    oldFrontPeriodUid = null;
    loading = newPeriodHolder;
    length++;
    return newPeriodHolder.mediaPeriod;
  }

而MediaPeriodHolder的构造函数是:

  public MediaPeriodHolder(
      RendererCapabilities[] rendererCapabilities,
      long rendererPositionOffsetUs,
      TrackSelector trackSelector,
      Allocator allocator,
      MediaSource mediaSource,
      MediaPeriodInfo info) {
    this.rendererCapabilities = rendererCapabilities;
    this.rendererPositionOffsetUs = rendererPositionOffsetUs;
    this.trackSelector = trackSelector;
    this.mediaSource = mediaSource;
    this.uid = info.id.periodUid;
    this.info = info;
    sampleStreams = new SampleStream[rendererCapabilities.length];
    mayRetainStreamFlags = new boolean[rendererCapabilities.length];
    mediaPeriod =
        createMediaPeriod(
            info.id, mediaSource, allocator, info.startPositionUs, info.endPositionUs);
  }

接着是createMediaPeriod():

  /** Returns a media period corresponding to the given {@code id}. */
  private static MediaPeriod createMediaPeriod(
      MediaPeriodId id,
      MediaSource mediaSource,
      Allocator allocator,
      long startPositionUs,
      long endPositionUs) {
    MediaPeriod mediaPeriod = mediaSource.createPeriod(id, allocator, startPositionUs);
    if (endPositionUs != C.TIME_UNSET && endPositionUs != C.TIME_END_OF_SOURCE) {
      mediaPeriod =
          new ClippingMediaPeriod(
              mediaPeriod, /* enableInitialDiscontinuity= */ true, /* startUs= */ 0, endPositionUs);
    }
    return mediaPeriod;
  }

然后就是MediaSource.createPeriod()方法,我们用ProgressiveMediaPeriod来看:

  public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
    DataSource dataSource = dataSourceFactory.createDataSource();
    if (transferListener != null) {
      dataSource.addTransferListener(transferListener);
    }
    return new ProgressiveMediaPeriod(
        uri,
        dataSource,
        extractorsFactory.createExtractors(),
        loadableLoadErrorHandlingPolicy,
        createEventDispatcher(id),
        this,
        allocator,
        customCacheKey,
        continueLoadingCheckIntervalBytes);
  }

再看MediaPeriod.prepare部分

这里还是用普通视频的ProgressiveMediaPeriod来看:

  public void prepare(Callback callback, long positionUs) {
    this.callback = callback;
    loadCondition.open();
    // 又调用了startLoading()
    startLoading();
  }

接着看startLoading()方法:

  private void startLoading() {
    // 注意这个loadable,里面有uri和DataSource
    ExtractingLoadable loadable =
        new ExtractingLoadable(
            uri, dataSource, extractorHolder, /* extractorOutput= */ this, loadCondition);
    if (prepared) {
      SeekMap seekMap = getPreparedState().seekMap;
      Assertions.checkState(isPendingReset());
      if (durationUs != C.TIME_UNSET && pendingResetPositionUs > durationUs) {
        loadingFinished = true;
        pendingResetPositionUs = C.TIME_UNSET;
        return;
      }
      loadable.setLoadPosition(
          seekMap.getSeekPoints(pendingResetPositionUs).first.position, pendingResetPositionUs);
      pendingResetPositionUs = C.TIME_UNSET;
    }
    extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount();
    // loader是构造函数里面创建的一个加载线程
    long elapsedRealtimeMs =
        loader.startLoading(
            loadable, this, loadErrorHandlingPolicy.getMinimumLoadableRetryCount(dataType));
    eventDispatcher.loadStarted(
        loadable.dataSpec,
        C.DATA_TYPE_MEDIA,
        C.TRACK_TYPE_UNKNOWN,
        /* trackFormat= */ null,
        C.SELECTION_REASON_UNKNOWN,
        /* trackSelectionData= */ null,
        /* mediaStartTimeUs= */ loadable.seekTimeUs,
        durationUs,
        elapsedRealtimeMs);
  }

接着看一下Loader类的startLoading()方法:

  public <T extends Loadable> long startLoading(
      T loadable, Callback<T> callback, int defaultMinRetryCount) {
    Looper looper = Looper.myLooper();
    Assertions.checkState(looper != null);
    fatalError = null;
    long startTimeMs = SystemClock.elapsedRealtime();
    new LoadTask<>(looper, loadable, callback, defaultMinRetryCount, startTimeMs).start(0);
    return startTimeMs;
  }

里面启动了LoadTask,LoadTask是一个Runnable的实现类,我们就直接看它的run方法:

private final class LoadTask<T extends Loadable> extends Handler implements Runnable {
    public void run() {
      try {
        executorThread = Thread.currentThread();
        if (!canceled) {
          TraceUtil.beginSection("load:" + loadable.getClass().getSimpleName());
          try {
            // 最终调用的是这个,而loadable是啥?他是上面创建的ExtractingLoadable
            loadable.load();
          } finally {
            TraceUtil.endSection();
          }
        }
        if (!released) {
          sendEmptyMessage(MSG_END_OF_SOURCE);
        }
      } catch (IOException e) {
        if (!released) {
          obtainMessage(MSG_IO_EXCEPTION, e).sendToTarget();
        }
      } catch (InterruptedException e) {
        // The load was canceled.
        Assertions.checkState(canceled);
        if (!released) {
          sendEmptyMessage(MSG_END_OF_SOURCE);
        }
      } catch (Exception e) {
        // This should never happen, but handle it anyway.
        Log.e(TAG, "Unexpected exception loading stream", e);
        if (!released) {
          obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget();
        }
      } catch (OutOfMemoryError e) {
        // This can occur if a stream is malformed in a way that causes an extractor to think it
        // needs to allocate a large amount of memory. We don't want the process to die in this
        // case, but we do want the playback to fail.
        Log.e(TAG, "OutOfMemory error loading stream", e);
        if (!released) {
          obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget();
        }
      } catch (Error e) {
        // We'd hope that the platform would kill the process if an Error is thrown here, but the
        // executor may catch the error (b/20616433). Throw it here, but also pass and throw it from
        // the handler thread so that the process dies even if the executor behaves in this way.
        Log.e(TAG, "Unexpected error loading stream", e);
        if (!released) {
          obtainMessage(MSG_FATAL_ERROR, e).sendToTarget();
        }
        throw e;
      }
    }

}

要想看ExtractingLoadable.load()方法,需要先看一下ExtractingLoadable类,以及他的构造函数:

/** Loads the media stream and extracts sample data from it. */
final class ExtractingLoadable implements Loadable, IcyDataSource.Listener {
    // DataSource是从流中读取数据的部分
    public ExtractingLoadable(
        Uri uri,
        DataSource dataSource,
        ExtractorHolder extractorHolder,
        ExtractorOutput extractorOutput,
        ConditionVariable loadCondition) {
      this.uri = uri;
      this.dataSource = new StatsDataSource(dataSource);
      this.extractorHolder = extractorHolder;
      this.extractorOutput = extractorOutput;
      this.loadCondition = loadCondition;
      this.positionHolder = new PositionHolder();
      this.pendingExtractorSeek = true;
      this.length = C.LENGTH_UNSET;
      dataSpec = buildDataSpec(/* position= */ 0);
    }

    public void load() throws IOException, InterruptedException {
      int result = Extractor.RESULT_CONTINUE;
      while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
        ExtractorInput input = null;
        try {
          long position = positionHolder.position;
          // DataSpec是表示数据区域的对象,buildDataSpec会通过Uri和position来确定具体的数据区间
          dataSpec = buildDataSpec(position);
          // 获取数据的长度
          length = dataSource.open(dataSpec);
          if (length != C.LENGTH_UNSET) {
            length += position;
          }
          Uri uri = Assertions.checkNotNull(dataSource.getUri());
          icyHeaders = IcyHeaders.parse(dataSource.getResponseHeaders());
          DataSource extractorDataSource = dataSource;
          if (icyHeaders != null && icyHeaders.metadataInterval != C.LENGTH_UNSET) {
            extractorDataSource = new IcyDataSource(dataSource, icyHeaders.metadataInterval, this);
            icyTrackOutput = icyTrack();
            icyTrackOutput.format(ICY_FORMAT);
          }
          input = new DefaultExtractorInput(extractorDataSource, position, length);
          // Extractor是从视频容器中获取数据的,selectExtractor方法内部会去选择对应的Extractor
          // 如果是MP4的话,这里就会返回Mp4Extractor对象
          Extractor extractor = extractorHolder. (input, extractorOutput, uri);

          // MP3 live streams commonly have seekable metadata, despite being unseekable.
          if (icyHeaders != null && extractor instanceof Mp3Extractor) {
            ((Mp3Extractor) extractor).disableSeeking();
          }

          if (pendingExtractorSeek) {
            extractor.seek(position, seekTimeUs);
            pendingExtractorSeek = false;
          }
          while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
            loadCondition.block();
            // extractor不断从input流中取读取数据,如果是mp4extractor的话
            // 里面会先执行readAtomHeader,然后atomheader解析完毕后去执行processMoovAtom
            // 等moovatom执行完后就会通过message回调onPrepared方法
            result = extractor.read(input, positionHolder);
            if (input.getPosition() > position + continueLoadingCheckIntervalBytes) {
              position = input.getPosition();
              loadCondition.close();
              // 执行完成后会执行到onContinueLoadingRequestedRunnable中
              handler.post(onContinueLoadingRequestedRunnable);
            }
          }
        } finally {
          if (result == Extractor.RESULT_SEEK) {
            result = Extractor.RESULT_CONTINUE;
          } else if (input != null) {
            positionHolder.position = input.getPosition();
          }
          Util.closeQuietly(dataSource);
        }
      }
    }
}

我们看到数据加载完成后会执行到onContinueLoadingRequestedRunnable中:
而该Runnable的实现:

onContinueLoadingRequestedRunnable =
    () -> {
      if (!released) {
        Assertions.checkNotNull(callback)
            .onContinueLoadingRequested(ProgressiveMediaPeriod.this);
      }
    };

会调用到onContinueLoadingRequested,该实现类就是ExoPlayerImplInternal类:

  @Override
  public void onContinueLoadingRequested(MediaPeriod source) {
    handler.obtainMessage(MSG_SOURCE_CONTINUE_LOADING_REQUESTED, source).sendToTarget();
  }

又是发消息,我们继续看下handleMessage方法里面的MSG_SOURCE_CONTINUE_LOADING_REQUESTED,收到该message会执行以下方法:

private void handleContinueLoadingRequested(MediaPeriod mediaPeriod) {
    if (!queue.isLoading(mediaPeriod)) {
      // Stale event.
      return;
    }
    queue.reevaluateBuffer(rendererPositionUs);
    // 尝试去加载下一个MediaPeriod
    maybeContinueLoading();
}

  private void maybeContinueLoading() {
    MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
    long nextLoadPositionUs = loadingPeriodHolder.getNextLoadPositionUs();
    if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
      setIsLoading(false);
      return;
    }
    long bufferedDurationUs =
        getTotalBufferedDurationUs(/* bufferedPositionInLoadingPeriodUs= */ nextLoadPositionUs);
    boolean continueLoading =
        loadControl.shouldContinueLoading(
            bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
    setIsLoading(continueLoading);
    if (continueLoading) {
      loadingPeriodHolder.continueLoading(rendererPositionUs);
    }
  }

到这里基本讲完了doSomeWork()部分。

总结一下:

  1. SimpleExoPlayer.prepare()里面调用ExoPlayerImpl.prepare()然后再调用ExoPlayerImplInternal.prepare()然后ExoPlayerImplInternal里面通过handler发送MSG_PREPARE,收到message后会执行prepareInternal()方法,这里面先是调用MediaSouorce.prepare去做一些初始化操作,然后再发送MSG_DO_SOME_WORK,收到改消息后执行doSomeWork()(这个方法执行完后会发消息,再循环执行doSomeWork()方法),这里面再调用updatePeriods()方法。
  2. 这个updatePeroid()方法呢灰常重要,里面又是三步: 第一个步骤是去加载MediaPeroid,然后调用MediaPeriod.prepare()方法,这里面又调用ExtractingLoadable去加载数据,里面会用DataSource和Extractor去解析数据(解析mp4文件的moov数据后执行onPrepared回调)。 第二步是在playWhenReady后,会执行queue.advancePlayingPeriod(),这里面会去设置要播放的playingMediaPeriodHolder。第三部是执行updatePlayingPeriodRenderers()方法,这里面会取playingMediaPeriodHolder然后调用enableRenderers()方法,该方法里面循环遍历renderers数组,对每个都调用renderer.start()。

下一篇文章会通过序列图的方式来描述一下这部分的调用逻辑。

上一篇: 2. ExoPlayer MediaSource简介
下一篇: 4. ExoPlayer源码分析之prepare序列图


results matching ""

    No results matching ""